﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Linq.Expressions;
using System.Text;
using System.Threading.Tasks;
using Xejen.Database.Entities;
using Xejen.Database.Exceptions;

namespace Xejen.Database.Ef
{
    /// <inheritdoc cref="IRepository{TEntity, TPrimaryKey}"/>
    public abstract class RepositoryBase<TEntity, TPrimaryKey> : RepositoryBase, IRepository<TEntity, TPrimaryKey>
        where TPrimaryKey : struct
        where TEntity : class, IEntity<TPrimaryKey>
    {
        /// <inheritdoc cref="RepositoryBase{TEntity, TPrimaryKey}"/>
        protected RepositoryBase()
        {

        }

        /// <inheritdoc/>
        public abstract IQueryable<TEntity> GetAll();

        /// <inheritdoc/>
        public abstract Task<IQueryable<TEntity>> GetAllAsync();

        /// <inheritdoc/>
        public virtual IQueryable<TEntity> GetAllIncluding(params Expression<Func<TEntity, object>>[] propertySelectors)
        {
            return GetAll();
        }

        /// <inheritdoc/>
        public virtual Task<IQueryable<TEntity>> GetAllIncludingAsync(params Expression<Func<TEntity, object>>[] propertySelectors)
        {
            return GetAllAsync();
        }

        /// <inheritdoc/>
        public virtual List<TEntity> GetAllList()
        {
            return GetAll().ToList();
        }

        /// <inheritdoc/>
        public virtual Task<List<TEntity>> GetAllListAsync()
        {
            return Task.FromResult(GetAllList());
        }

        /// <inheritdoc/>
        public virtual List<TEntity> GetAllList(Expression<Func<TEntity, bool>> predicate)
        {
            return GetAll().Where(predicate).ToList();
        }

        /// <inheritdoc/>
        public virtual Task<List<TEntity>> GetAllListAsync(Expression<Func<TEntity, bool>> predicate)
        {
            return Task.FromResult(GetAllList(predicate));
        }

        /// <inheritdoc/>
        public virtual T Query<T>(Func<IQueryable<TEntity>, T> queryMethod)
        {
            return queryMethod(GetAll());
        }

        /// <inheritdoc/>
        public virtual TEntity Get(TPrimaryKey id)
        {
            var entity = FirstOrDefault(id);
            if (entity == null)
            {
                throw new EntityNotFoundException(typeof(TEntity), id);
            }

            return entity;
        }

        /// <inheritdoc/>
        public virtual async Task<TEntity> GetAsync(TPrimaryKey id)
        {
            var entity = await FirstOrDefaultAsync(id);
            if (entity == null)
            {
                throw new EntityNotFoundException(typeof(TEntity), id);
            }

            return entity;
        }

        /// <inheritdoc/>
        public virtual TEntity Single(Expression<Func<TEntity, bool>> predicate)
        {
            return GetAll().Single(predicate);
        }

        /// <inheritdoc/>
        public virtual Task<TEntity> SingleAsync(Expression<Func<TEntity, bool>> predicate)
        {
            return Task.FromResult(Single(predicate));
        }

        /// <inheritdoc/>
        public virtual TEntity FirstOrDefault(TPrimaryKey id)
        {
            return GetAll().FirstOrDefault(CreateEqualityExpressionForId(id));
        }

        /// <inheritdoc/>
        public virtual Task<TEntity> FirstOrDefaultAsync(TPrimaryKey id)
        {
            return Task.FromResult(FirstOrDefault(id));
        }

        /// <inheritdoc/>
        public virtual TEntity FirstOrDefault(Expression<Func<TEntity, bool>> predicate)
        {
            return GetAll().FirstOrDefault(predicate);
        }

        /// <inheritdoc/>
        public virtual Task<TEntity> FirstOrDefaultAsync(Expression<Func<TEntity, bool>> predicate)
        {
            return Task.FromResult(FirstOrDefault(predicate));
        }

        /// <inheritdoc/>
        public virtual TEntity Load(TPrimaryKey id)
        {
            return Get(id);
        }

        /// <inheritdoc/>
        public abstract TEntity Insert(TEntity entity);

        /// <inheritdoc/>
        public virtual Task<TEntity> InsertAsync(TEntity entity)
        {
            return Task.FromResult(Insert(entity));
        }

        /// <inheritdoc/>
        public virtual TPrimaryKey InsertAndGetId(TEntity entity)
        {
            return Insert(entity).Id;
        }

        /// <inheritdoc/>
        public virtual async Task<TPrimaryKey> InsertAndGetIdAsync(TEntity entity)
        {
            var insertedEntity = await InsertAsync(entity);
            return insertedEntity.Id;
        }

        /// <inheritdoc/>
        public virtual TEntity InsertOrUpdate(TEntity entity)
        {
            return entity.IsTransient()
                ? Insert(entity)
                : Update(entity);
        }

        /// <inheritdoc/>
        public virtual async Task<TEntity> InsertOrUpdateAsync(TEntity entity)
        {
            return entity.IsTransient()
                ? await InsertAsync(entity)
                : await UpdateAsync(entity);
        }

        /// <inheritdoc/>
        public virtual TPrimaryKey InsertOrUpdateAndGetId(TEntity entity)
        {
            return InsertOrUpdate(entity).Id;
        }

        /// <inheritdoc/>
        public virtual async Task<TPrimaryKey> InsertOrUpdateAndGetIdAsync(TEntity entity)
        {
            var insertedEntity = await InsertOrUpdateAsync(entity);
            return insertedEntity.Id;
        }

        /// <inheritdoc/>
        public abstract TEntity Update(TEntity entity);

        /// <inheritdoc/>
        public virtual Task<TEntity> UpdateAsync(TEntity entity)
        {
            return Task.FromResult(Update(entity));
        }

        /// <inheritdoc/>
        public virtual TEntity Update(TPrimaryKey id, Action<TEntity> updateAction)
        {
            var entity = Get(id);
            updateAction(entity);
            return entity;
        }

        /// <inheritdoc/>
        public virtual async Task<TEntity> UpdateAsync(TPrimaryKey id, Func<TEntity, Task> updateAction)
        {
            var entity = await GetAsync(id);
            await updateAction(entity);
            return entity;
        }

        /// <inheritdoc/>
        public abstract void Delete(TEntity entity);

        /// <inheritdoc/>
        public virtual Task DeleteAsync(TEntity entity)
        {
            Delete(entity);
            return Task.CompletedTask;
        }

        /// <inheritdoc/>
        public abstract void Delete(TPrimaryKey id);

        /// <inheritdoc/>
        public virtual Task DeleteAsync(TPrimaryKey id)
        {
            Delete(id);
            return Task.CompletedTask;
        }

        /// <inheritdoc/>
        public virtual void Delete(Expression<Func<TEntity, bool>> predicate)
        {
            //foreach (var entity in GetAllList(predicate))
            //{
            //    Delete(entity);
            //}

            IEnumerable<TEntity> items = GetAll().Where(predicate);
            foreach (TEntity entity in items)
            {
                Delete(entity);
            }
        }

        /// <inheritdoc/>
        public virtual async Task DeleteAsync(Expression<Func<TEntity, bool>> predicate)
        {
            //var entities = await GetAllListAsync(predicate);
            //foreach (var entity in entities)
            //{
            //    await DeleteAsync(entity);
            //}

            IEnumerable<TEntity> items = GetAll().Where(predicate);
            foreach (TEntity entity in items)
            {
                await DeleteAsync(entity);
            }
        }

        /// <inheritdoc/>
        public virtual int Count()
        {
            return GetAll().Count();
        }

        /// <inheritdoc/>
        public virtual Task<int> CountAsync()
        {
            return Task.FromResult(Count());
        }

        /// <inheritdoc/>
        public virtual int Count(Expression<Func<TEntity, bool>> predicate)
        {
            return GetAll().Count(predicate);
        }

        /// <inheritdoc/>
        public virtual Task<int> CountAsync(Expression<Func<TEntity, bool>> predicate)
        {
            return Task.FromResult(Count(predicate));
        }

        /// <inheritdoc/>
        public virtual long LongCount()
        {
            return GetAll().LongCount();
        }

        /// <inheritdoc/>
        public virtual Task<long> LongCountAsync()
        {
            return Task.FromResult(LongCount());
        }

        /// <inheritdoc/>
        public virtual long LongCount(Expression<Func<TEntity, bool>> predicate)
        {
            return GetAll().LongCount(predicate);
        }

        /// <inheritdoc/>
        public virtual Task<long> LongCountAsync(Expression<Func<TEntity, bool>> predicate)
        {
            return Task.FromResult(LongCount(predicate));
        }

        /// <inheritdoc/>
        protected virtual Expression<Func<TEntity, bool>> CreateEqualityExpressionForId(TPrimaryKey id)
        {
            var lambdaParam = Expression.Parameter(typeof(TEntity));

            var leftExpression = Expression.PropertyOrField(lambdaParam, "Id");

            var idValue = Convert.ChangeType(id, typeof(TPrimaryKey));

            Expression<Func<object>> closure = () => idValue;
            var rightExpression = Expression.Convert(closure.Body, leftExpression.Type);

            var lambdaBody = Expression.Equal(leftExpression, rightExpression);

            return Expression.Lambda<Func<TEntity, bool>>(lambdaBody, lambdaParam);
        }

        /// <inheritdoc/>
        public abstract int SaveChanges();

        /// <inheritdoc/>
        public abstract Task<int> SaveChangesAsync();
    }
}
